#include "transContentConnect.hpp"

/////////////////////////////////////////////////////////////////////////////
/// construct connect request packet to target
///		send to target use local buffer, since bytes less than 100 bytes
///		the method finish untill the connect finshed: after create and init the stream port
///
/// connect request out packet content format
///  name | total len | mainCode | target | flag | length | mainCode | subCode | remotePort| local port | local addr |
///  len  |    2      |   1      |   8    |   1  |    2   |     1    |    1    |     4     |     4      |     8      |
/// offset|    0      |   2      |   3    |   11 |    12  |     14   |    15   |    16     |     20     |     24     |
///
/// return:
///		E_TRANC_ParamError: check the need fileds to construct the request packet failed
///		E_TRANC_InnerLogicError: the connect port record state not changed, so some logic error occured
///		E_TRANC_NONBLOCK: the caller use sync method
///		E_TRANC_TIMEOUT: the opearation timeout
///		vid: the connect sucessfully and the vid of the stream port
/////////////////////////////////////////////////////////////////////////////
vroute_t TransContentConnect::connect(){
	char buffer[36] = {0};
	timespec ts;
	debug_enter();
	
	/// check need fields
	if(localPort <= 0 || targetPort <= 0 || targetNode.serial_id <= 0){
		logd_error("the must exist filed check failed!");
		return E_TRANC_ParamError;
	}

	/// check state
	if(state != 0){
		logd_error("the port: %d is already connecting, can not call it again", localPort);
		return E_TRANC_STATE_BUSY;
	}

	/// change state
	if(state == 0){
		state = 1;
	}

	/// construct the packet
	logd_info("begin to construct the connect packet");
	*(short*(buffer)) = (short)30;
	buffer[2] = (char)message_type_accept-connect;
	*(NodeAddr*(buffer+3)) = targetNode;
	buffer[11] = (char)CLIENT_STACK_FLAG_BITS_FIXEDPATH;
	*(short*(buffer+12)) = TransContentAssis::SHORTL2N(18);
	buffer[14] = (char)message_type_accept-connect;
	buffer[15] = (char)message_type_accept-connect_sub_streamStart;
	*(int*(buffer+16)) = TransContentAssis::INTL2N(targetPort);
	*(int*(buffer+20)) = TransContentAssis::INTL2N(localPort);
	*(NodeAddr*(buffer+24)) = TransContentSetting::getLocalAddrNetBytes();
	logd_info("construct connect packet finished, send to stack!");
	
	/// send to stack
	TransContentDispatcher::getInstance()->sendDataDataToStack(buffer, 32);

	/// block or send
	if(callback != NULL){
		logd_info("call connect method use sync style, the caller will not be blocked");
		return E_TRANC_NONBLOCK;
	}

	/// block
	ts = TransContentAssis::TimeTp2Ts(timeout);
	pthread_mutex_lock(&waitMutex);
	if(pthread_cond_timedwait(&waitCond, &waitMutex, &ts) == ETIMEDOUT){
		logd_error("wait connect response timed out, the port manager should release the connect instance, and chagne the port state");
		return RESPONSE_RESULT_RESET_DEFAULT;
	}
	pthread_mutex_unlock(&waitMutex);

	/// connect finished
	logd_info("connect sucessfully, the connect will finished");
	canBeRemoved = 1;
	return this->vid;
}

////////////////////////////////////////////////////////////////////////////////
/// extract filed information from remote accept response
///		the most important filed is the result
///
/// parameters:
///		content: the response content buffer
///		index: the buffer index in memory pool
///
///	return:
///		RESPONSE_RESULT_NOACTION: the port manager should do nothing
///		RESPONSE_RESULT_RESET_DEFAULT: the port manager should reset the port state to regsited
///		RESPONSE_RESULT_CREATE_STREAM: the port manager should create a new stream port record, use the same port
//////////////////////////////////////////////////////////////////////////////////
int TransContentConnect::response(char* content, int index){
	int subCode;
	int mainCode;
	debug_enter();

	mainCode = content[0];
	if(mainCode != message_type_accept-connect){
		logd_error("connect, not support main code:%d", mainCode);
		return RESPONSE_RESULT_POST_CONITNUE;
	}

	if(state != 1){
		logd_error("response state check error, state != 1, means have already received accept ack");
		TransContentMemory::getInstance()->releaseBuffer(index);
		return RESPONSE_RESULT_RELEASEBUFFER;
	}

	state = 2; //connecting-createstream

	subCode = content[1];
	logd_info("get connect response: sub message code: %d", subCode);
	switch(subCode){
		case message_type_accept-connect_sub_connectAck:
			logd_info("deal sub message for connet ack--accept refused");
			result = E_TRANC_ACCEPT_REFUSE;
			break;
		case message_type_accept-connect_sub_streamStart:
			logd_info("deal sub message for conect stream start");
			extractRemoteInfo(content);
			result = 1;
		default:
			logd_error("not support sub message code: %d", subCode);
	}
	/// remove buffer
	TransContentMemory::getInstance()->releaseBuffer(index);
	
	/// if error, almost is the remote refuse
	if(result != 1){
		logd_error("extract information from remote: get the result is error: %d, connect finihsed", result);
		vid = result;
		connectFinishe();
		return RESPONSE_RESULT_RESET_DEFAULT;
	}

	/// remote accept sucessfully
	logd_info("remote accept sucessfully, the port manager should create a new stream record");
	return RESPONSE_RESULT_CREATE_STREAM;
}

void TransContentConnect::extractRemoteInfo(char* content){
	peer->localPort = TransContentAssis::INTN2L(*(int*(content+2)));
	peer->remotePort = TransContentAssis::INTN2L(*(int*(content+6)));
	peer->remoteAddr = TransContentAssis::NodeAddrN2L(*(NodeAddr*(content+10)));
	remoteBufferSize = content[22];
	remoteSteramStart = TransContentAssis::INTN2L(*(int*(content+18)));
}

//////////////////////////////////////////////////////////////////////////////////////
/// connect finished
///		let blocked thread go again, or create a thread to call callback
/////////////////////////////////////////////////////////////////////////////////////
void TransContentConnect::connectFinishe(){
	pthread_t pid;
	debug_enter();
	if(callback != NULL){
		pthread_create(&pid, NULL, finishRoute, this);
		pthread_detach(pid);
	}else{
		pthread_mutex_lock(&waitMutex);
		pthread_cond_signal(&waitCond);
		pthread_mutex_unlock(&waitMutex);
	}
	debug_leave();
}

void* finishRoute(void* p){
	(TransContentConnect*(p))->finishCallback();
	return NULL;
}

void finishCallback(){
	callback(result, vid);
	canBeRemoved = 1;
}
